#pragma once
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <queue>

// version1 原始锁+原始信号量:

// template<typename T>
// class BlockQueue
// {
//     public :
//         static const int defaultnum=10;
//         bool IsFull() {return _q.size()==_cap;}
//         bool IsEmpty(){return _q.size()==0;}

//         BlockQueue(const int& cap=defaultnum)
//         :_cap(cap),_cwait_num(0),_pwait_num(0)
//         {
//             pthread_mutex_init(&_mutex,nullptr);
//             pthread_cond_init(&_ProductCond,nullptr);
//             pthread_cond_init(&_ConsumeCond,nullptr);
//         }
//         void Equeue(const T& in )   // 生产者使用接口，将传入的in加入到队列中
//         {
//             // 添加临界资源，先要进行互斥，
//             pthread_mutex_lock(&_mutex);
//             while(IsFull())  // 如果是满的则阻塞，用while是为了防止伪唤醒
//             // 伪唤醒： 线程可能会因为OS的线程调度策略或底层硬件中断导致线程意外唤醒。
//             //         进程也可能收到一些无关的信号，这些信号也可能导致在条件变量上的线程被唤醒
//             {
//                 // 进到这里说明队列是满的
//                 std::cout <<"生产者进入等待..."<<std::endl;
//                 // 等时，释放锁
//                 _pwait_num++;
//                 pthread_cond_wait(&_ProductCond,&_mutex);   // wait时，必定是持有锁的，否则是有问题的。
//                 _pwait_num--;   // 到这里正常情况下是，消费者消费一个，唤醒的。
//                 // 线程被唤醒，并重新申请并持有锁（它会在临界区醒来）
//                 std::cout<<"生产者被唤醒..."<<std::endl;
//             }
//             // 到这里 BlockQueue一定不是满的，并且也一定持有锁
//             //生产
//             _q.push(in);

//             // 如果有消费者在等待，则发送信号给他们
//             if(_cwait_num>0)
//             {
//                 //这时BlockQueue中一定有资源,唤醒一个消费者
//                 std::cout<<"叫醒一个消费者..."<<std::endl;
//                 pthread_cond_signal(&_ConsumeCond);
//             }
//             pthread_mutex_unlock(&_mutex);
//         }
//         void Pop(T* out) // 消费者使用接口，通过out将队首的资源带出
//         {
//             //访问临界区先上锁
//             pthread_mutex_lock(&_mutex);
//             //判断队列是否为空，并且为防止伪唤醒，采用while
//             while(IsEmpty())
//             {
//                 //进到这里说明队列为空
//                  std::cout<<"消费者进入等待..."<<std::endl;
//                  _cwait_num++;
//                  pthread_cond_wait(&_ConsumeCond,&_mutex);
//                  _cwait_num--;
//                  std::cout<<"消费者被唤醒了..."<<std::endl;
//             }
//             // 到这里说明队列不为空，并且当前线程被唤醒了，并重新持有锁
//             //消费
//             *out=_q.front();
//             _q.pop();

//             //判断当前是否有生产者进入了等待
//             if(_pwait_num>0)
//             {
//                 std::cout<<"唤醒了一个生产者..."<<std::endl;
//                 pthread_cond_signal(&_ProductCond);
//             }
//             // 释放锁
//             pthread_mutex_unlock(&_mutex);

//         }
//         ~BlockQueue()
//         {
//             pthread_mutex_destroy(&_mutex);
//             pthread_cond_destroy(&_ProductCond);
//             pthread_cond_destroy(&_ConsumeCond);
//         }
//     private:
//         std::queue<T> _q;   // 保存数据的容器，临界资源
//         int _cap;       // BlockQueue的最大容量
//         pthread_mutex_t _mutex;  //互斥
//         pthread_cond_t _ProductCond; // 生产者条件变量
//         pthread_cond_t _ConsumeCond; // 消费者条件变量

//         int _cwait_num;     // 消费者正在等待的个数
//         int _pwait_num;     // 生产者正在等待的个数

// };

// version2 在version1的基础上增加水平块:

// template<typename T>
// class BlockQueue
// {
//     public :
//         static const int defaultnum=10;
//         bool IsFull() {return _q.size()==_cap;}
//         bool IsEmpty(){return _q.size()==0;}

//         BlockQueue(int cnum,const int cap=defaultnum)
//         :_cap(cap),_low_water(_cap*2/3),_high_water(_cap/3),_cnum(cnum)
//         {
//             pthread_mutex_init(&_mutex,nullptr);
//             pthread_cond_init(&_ProductCond,nullptr);
//             pthread_cond_init(&_ConsumeCond,nullptr);
//         }
//         void Equeue(const T& in )   // 生产者使用接口，将传入的in加入到队列中
//         {
//             // 添加临界资源，先要进行互斥，
//             pthread_mutex_lock(&_mutex);
//             while(IsFull())  // 如果是满的则阻塞，用while是为了防止伪唤醒
//             // 伪唤醒： 线程可能会因为OS的线程调度策略或底层硬件中断导致线程意外唤醒。
//             //         进程也可能收到一些无关的信号，这些信号也可能导致在条件变量上的线程被唤醒
//             {
//                 // 进到这里说明队列是满的
//                std::cout <<"生产者进入等待..."<<std::endl;
//                 pthread_cond_wait(&_ProductCond,&_mutex);   // wait时，必定是持有锁的，否则是有问题的。
//                 std::cout<<"生产者被唤醒..."<<std::endl;
//             }
//             // 到这里 BlockQueue一定不是满的，并且也一定持有锁
//             //生产
//             _q.push(in);

//             // 如果有消费者在等待，则发送信号给他们
//             if(_q.size()>=_high_water)   // 开始唤醒消费者（当p的容量达到最大值时，生产者开始等待，直到p.size()<low_water才开始唤醒）
//             {
//                 //这时BlockQueue中一 定有资源,唤醒一个消费者
//                 std::cout<<"叫醒一个消费者..."<<std::endl;
//                 pthread_cond_signal(&_ConsumeCond); 
//             }
//              else if(_cnum<=0) 
//              {
//                 std::cout<<"消费者全部沉睡,唤醒一个消费者"<<std::endl;
//                 pthread_cond_signal(&_ConsumeCond);
//              }
//             pthread_mutex_unlock(&_mutex);
//         }
//         void Pop(T* out) // 消费者使用接口，通过out将队首的资源带出
//         {
//             //访问临界区先上锁
//             pthread_mutex_lock(&_mutex);
//             //判断队列是否为空，并且为防止伪唤醒，采用while
//             while(IsEmpty())
//             {
//                 //进到这里说明队列为空
//                  std::cout<<"消费者进入等待..."<<std::endl;
//                _cnum--;
//                  pthread_cond_wait(&_ConsumeCond,&_mutex);
//                _cnum++;
//                        std::cout<<"消费者被唤醒了..."<<std::endl;
//             }
//             // 到这里说明队列不为空，并且当前线程被唤醒了，并重新持有锁
//             //消费
//             *out=_q.front();
//             _q.pop();
//             //判断当前是否有生产者进入了等待
//             if(_q.size()<=_low_water)   //    如果低于低水平线，开始唤醒生产者（当p.size()的值等于0则生产者开始等待，直到p.size()>high_water）
//             {
//                 std::cout<<"唤醒了一个生产者..."<<std::endl;
//                 pthread_cond_signal(&_ProductCond);
//             } 
//             // 释放锁
//             pthread_mutex_unlock(&_mutex);
//         }
//         ~BlockQueue()
//         {
//             pthread_mutex_destroy(&_mutex);
//             pthread_cond_destroy(&_ProductCond);
//             pthread_cond_destroy(&_ConsumeCond);
//         }
//     private:
//         std::queue<T> _q;   // 保存数据的容器，临界资源
//         int _cap;       // BlockQueue的最大容量
//         pthread_mutex_t _mutex;  //互斥
//         pthread_cond_t _ProductCond; // 生产者条件变量
//         pthread_cond_t _ConsumeCond; // 消费者条件变量

//         // int _cwait_num;     // 消费者正在等待的个数
//         // int _pwait_num;     // 生产者正在等待的个数
//         int _cnum;      // 消费者正在消费的线程数
//         int _low_water;
//         int _high_water;
// };

//version3: versino1的基础上 使用我们封装的锁和信号量
#include "Mutex.hpp"
#include "Cond.hpp"

using namespace MutexMoudle;
using namespace CondMoudle;

template <typename T>
class BlockQueue
{
public:
    static const int defaultnum = 10;
    bool IsFull() { return _q.size() == _cap; }
    bool IsEmpty() { return _q.size() == 0; }

    BlockQueue(const int cap = defaultnum)
        : _cap(cap),_pwait_num(0),_cwait_num(0)
    {
    }
    void Equeue(const T &in) // 生产者使用接口，将传入的in加入到队列中
    {
        // 添加临界资源，先要进行互斥，
        LockGuard lockguard(_mutex);
        while (IsFull()) // 如果是满的则阻塞，用while是为了防止伪唤醒
        // 伪唤醒： 线程可能会因为OS的线程调度策略或底层硬件中断导致线程意外唤醒。
        //         进程也可能收到一些无关的信号，这些信号也可能导致在条件变量上的线程被唤醒
        {
            // 进到这里说明队列是满的
            //    std::cout <<"生产者进入等待..."<<std::endl;
            // 等时，释放锁
                _pwait_num++;
            _ProductCond.Wait(_mutex); // 我们封装的之间使用都是适配的，不需要再调用底层的原始锁指针
            // 线程被唤醒，并重新申请并持有锁（它会在临界区醒来）
                _pwait_num--;   
            //     std::cout<<"生产者被唤醒..."<<std::endl;
        }
        // 到这里 BlockQueue一定不是满的，并且也一定持有锁
        // 生产
        _q.push(in);

        // 如果有消费者在等待，则发送信号给他们
        if (_cwait_num>0) 
        {
            // 这时BlockQueue中一定有资源,唤醒一个消费者
            //     std::cout<<"叫醒一个消费者..."<<std::endl;
            _ConsumeCond.Notify();
        }
    }
    void Pop(T *out) // 消费者使用接口，通过out将队首的资源带出
    {
        // 访问临界区先上锁
        LockGuard lockguard(_mutex);
        // 判断队列是否为空，并且为防止伪唤醒，采用while
        while (IsEmpty())
        {
            // 进到这里说明队列为空
            //     std::cout<<"消费者进入等待..."<<std::endl;
             _cwait_num++;
            _ConsumeCond.Wait(_mutex);
             _cwait_num--;
            //      std::cout<<"消费者被唤醒了..."<<std::endl;
        }
        // 到这里说明队列不为空，并且当前线程被唤醒了，并重新持有锁
        // 消费
        *out = _q.front();
        _q.pop();
        // 判断当前是否有生产者进入了等待
        if (_pwait_num>0)
        {
            //       std::cout<<"唤醒了一个生产者..."<<std::endl;
            _ProductCond.Notify();
        }
        // 释放锁
    }
    ~BlockQueue()
    {
    }

private:
    std::queue<T> _q;  // 保存数据的容器，临界资源, _q只有一份，需要加锁，但是共享资源也可以被看作多份。
    int _cap;          // BlockQueue的最大容量
    Mutex _mutex;      // 互斥
    Cond _ProductCond; // 生产者条件变量
    Cond _ConsumeCond; // 消费者条件变量

     int _cwait_num;     // 消费者正在等待的个数
     int _pwait_num;     // 生产者正在等待的个数
   // int _low_water;
   // int _high_water;
};